package	model;

import editor.PwnIDE;
import editor.widgets.CustomProgressBar;
import event.SymbolChangeEvent;
import event.SymbolRenameEvent;
import lang.Encoder;
import lang.Language;
import lang.Loader;
import lang.asm.ASM;
import model.caret.Caret;
import model.caret.CaretRef;
import model.caret.HeaderCaret;
import model.caret.TextCaret;
import model.code.*;
import model.code.line.LineList;
import model.code.line.SingleLine;
import model.code.line.SyntaxError;
import model.symbol.Constant;
import model.symbol.Scope;
import model.symbol.ScopeObserver;
import model.symbol.Symbol;
import observer.ObservedInt;
import observer.ObservedList;
import util.FileUtil;
import xml.Element;
import xml.Tag;
import xml.XML;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.TreeMap;

/**
 * This object represents an open PwnIDE project.
 *
 * @author Neil Dickson
 */
public class Project implements ScopeObserver, SingleLine.LineObserver {
	/**
	 * The file in which project information is stored.
	 */
	private	File					projectFile;
	/**
	 * Root directory of the project; its name and description are those of the project, not its location.
	 */
	private	Directory				projectDirectory;

	/**
	 * Contains an integer representing what type of executable format to which to compile
	 * (e.g. 64-bit Windows DLL, OS image, or 64-bit Linux ELF file)
	 * The allowed values are defined in {@link Encoder}.
	 */
	private ObservedInt				outputType;
	/**
	 * Global {@link Function} that is the primary entry point for the generated executable file
	 */
	private	SingleLine				entryPoint;
	/**
	 * External include files that are not associated with a library or object file to include in the build
	 */
	private	ObservedList<ExternalInclude>	importedIncludes;
	/**
	 * External library or object files to be included in the build
	 */
	private	ObservedList<ExternalInclude>	libraryIncludes;

	/**
	 * Scope containing all global {@link Symbol}s; global Symbols should be accessible from anywhere in the Project.
	 */
	private	Scope					globalScope;

	/**
	 * List of files that currently have tabs open, in the order of their tabs
	 */
	private	ArrayList<ProjectFile>	openFiles;
	/**
	 * The file whose tab is currently active, or null if none are open
	 */
	private ProjectFile				activeFile;

	private	Segment					defaultCodeSegment;
	private	Segment					defaultReadOnlyDataSegment;
	private	Segment					defaultInitDataSegment;
	private	Segment					defaultUninitDataSegment;
	private	ObservedList<Segment>	segments;

	private HashSet<SyntaxError>	errors;
	private HashSet<Caret>			undefined;

	public static final String		DEFAULT_ENTRY_POINT_NAME = "main";

	public Project(File projectFile, CustomProgressBar progress) throws IOException {
		if (progress!=null) {
			progress.setStatus("Reading project file...",progress.getCompletion());
		}
		this.projectFile = projectFile;
		errors = new HashSet<SyntaxError>();
		undefined = new HashSet<Caret>();
		entryPoint = new SingleLine(DEFAULT_ENTRY_POINT_NAME.toCharArray(),null,CodeFileChunk.LOCATION_NOWHERE);
		entryPoint.addReference(null,0,false);
		entryPoint.addObserver(this);
		importedIncludes = new ObservedList<ExternalInclude>();
		libraryIncludes = new ObservedList<ExternalInclude>();

		XML xml = new XML(FileUtil.readFileToString(projectFile),true,false);
		Tag root = xml.getRoot();
		// TODO: Do some sort of version compatibility check here using the min_version attribute.
		globalScope = new Scope(Scope.TYPE_GLOBAL);
		globalScope.addObserver(this);
		if (root.hasAttribute("outputType")) {
			outputType = new ObservedInt(Integer.parseInt(root.getAttribute("outputType")));
		}
		else {
			outputType = new ObservedInt(Encoder.OUTPUT_TYPE_UNSPECIFIED);
		}

		segments = new ObservedList<Segment>();

		openFiles = new ArrayList<ProjectFile>();
		ArrayList<CodeFile> codeFilesToParse = new ArrayList<CodeFile>();
		ArrayList<Tag> caretTags = new ArrayList<Tag>();
		projectDirectory = parseDirectory(root,null,codeFilesToParse,caretTags);
		Loader.loadFiles(this,codeFilesToParse,progress);
		for (int i=0;i<caretTags.size();++i) {
			Tag caretTag = caretTags.get(i);
			CodeFile file = codeFilesToParse.get(i);
			if (caretTag!=null) {
				String caretType = caretTag.getAttribute("type");
				if (caretType.equals("text")) {
					CaretRef ref = file.getCaretRef();
					ArrayList<Symbol> symbols = file.getGlobalScope().get(caretTag.getAttribute("function"));
					if (symbols!=null) {
						for (Symbol symbol : symbols) {
							if (symbol instanceof CodeFileChunk && ((CodeFileChunk)symbol).getFile()==file) {
								int headLine = Integer.parseInt(caretTag.getAttribute("head_line"));
								int headCol = Integer.parseInt(caretTag.getAttribute("head_col"));
								int tailLine = Integer.parseInt(caretTag.getAttribute("tail_line"));
								int tailCol = Integer.parseInt(caretTag.getAttribute("tail_col"));
								int savedX = Integer.parseInt(caretTag.getAttribute("saved_x"));
								int locType = caretTag.hasAttribute("locType")?Integer.parseInt(caretTag.getAttribute("locType")):CodeFileChunk.LOCATION_CODE_PANEL;
								int locIndex = caretTag.hasAttribute("locIndex")?Integer.parseInt(caretTag.getAttribute("locIndex")):0;
								LineList lines = ((CodeFileChunk)symbol).getLineList(locType,locIndex);
								if (lines!=null) {
									ref.set(new TextCaret(ref,lines,headLine,headCol,tailLine,tailCol,savedX));
								}
								// If something screwed up, just select the header of the chunk instead
								else {
									HeaderCaret caret = new HeaderCaret(ref);
									caret.add((CodeFileChunk)symbol);
									ref.set(caret);
								}
								break;
							}
						}
					}
				}
				else if (caretType.equals("header")) {
					CaretRef ref = file.getCaretRef();
					HeaderCaret caret = new HeaderCaret(ref);
					String[] headers = caretTag.getAttribute("headers").split(",");
					// FIXME: Figure out a good way to indicate the header caret in text.
				}
			}
		}

		// If no segments were specified in the file, create the default ones.
		if (segments.size()==0) {
			// Create the default segments
			defaultCodeSegment = new Segment(Segment.DEFAULT_CODE_SEG_NAME,Segment.DEFAULT_N_BITS,Segment.CODE);
			defaultReadOnlyDataSegment = new Segment(Segment.DEFAULT_READ_ONLY_DATA_SEG_NAME,Segment.DEFAULT_N_BITS,Segment.READ_ONLY_DATA);
			defaultInitDataSegment = new Segment(Segment.DEFAULT_INIT_DATA_SEG_NAME,Segment.DEFAULT_N_BITS,Segment.INIT_DATA);
			defaultUninitDataSegment = new Segment(Segment.DEFAULT_UNINIT_DATA_SEG_NAME,Segment.DEFAULT_N_BITS,Segment.UNINIT_DATA);
			segments.add(defaultCodeSegment);
			// NOTE: The read-only data segment is put in between the code and the writable data to minimize cache flushing issues when writing to data.
			segments.add(defaultReadOnlyDataSegment);
			segments.add(defaultInitDataSegment);
			segments.add(defaultUninitDataSegment);
		}
	}

	private Directory parseDirectory(Tag directoryTag,Directory parent,ArrayList<CodeFile> codeFilesToParse,ArrayList<Tag> caretTags) throws IOException {
		Directory directory = new Directory(directoryTag.getAttribute("name"),directoryTag.hasAttribute("description") ? directoryTag.getAttribute("description") : "",parent,this);
		if (directoryTag.hasAttribute("expanded")) {
			directory.setExpanded(directoryTag.getAttribute("expanded").equals("true"));
		}
		if (parent!=null) {
			parent.add(directory);
		}
		String path = PwnIDE.getValidFilePath(directory.getPath());
		if (directoryTag.hasChildren()) {
			for (Element element : directoryTag.getChildren()) {
				if (element instanceof Tag) {
					Tag tag = (Tag)element;
					if (tag.getName().equals("directory")) {
						parseDirectory(tag,directory,codeFilesToParse,caretTags);
					}
					else if (tag.getName().equals("file")) {
						String name = tag.getAttribute("name");
						ProjectFile file = null;
						String type = tag.getAttribute("type");
						String filename = path+name;
						if (type.equals("document")) {
							// TODO: Handle documents once supported.
//							file = Language.parseDocument(filename+"Doc.txt");
//							directory.add((Document)file);
						}
						else if (Loader.NAME_TO_LANGUAGE_MAP.containsKey(type)) {
							Language language = Loader.NAME_TO_LANGUAGE_MAP.get(type);
							file = new CodeFile(name,language,directory,false,name.toLowerCase().endsWith("."+language.getDefaultIncludeExtension()));
							directory.add((CodeFile)file);
							((CodeFile)file).setProject(this);
							codeFilesToParse.add((CodeFile)file);
							boolean caretFound = false;
							if (tag.hasChildren()) {
								for (Element child : tag.getChildren()) {
									if (child instanceof Tag && ((Tag)child).getName().equals("caret")) {
										caretTags.add((Tag)child);
										caretFound = true;
										break;
									}
								}
							}
							if (!caretFound) {
								caretTags.add(null);
							}
						}
						else {
							// TODO: Inform the user of the file without its associated language.
						}
						if (tag.hasAttribute("open") && file!=null) {
							try {
								int openIndex = Integer.parseInt(tag.getAttribute("open"));
								while (openFiles.size()<=openIndex) {
									openFiles.add(null);
								}
								openFiles.set(openIndex,file);
								if (tag.hasAttribute("active") && tag.getAttribute("active").equals("true")) {
									activeFile = file;
								}
							}
							catch (NumberFormatException e) {
								// Don't worry about the exception, since having a file open isn't critical
							}
						}
					}
					else if (parent==null && tag.getName().equals("segment")) {
						addSegment(new Segment(tag.getAttribute("name"),
												Integer.parseInt(tag.getAttribute("nBits")),
												Integer.parseInt(tag.getAttribute("type")),
												tag.hasAttribute("alignment") ? Integer.parseInt(tag.getAttribute("alignment")) : Segment.DEFAULT_ALIGNMENT,
												tag.hasAttribute("address") ? Long.parseLong(tag.getAttribute("address"),16) : Segment.ANY_ADDRESS,
												tag.hasAttribute("maxSize") ? Integer.parseInt(tag.getAttribute("maxSize"),16) : Segment.NO_MAX_SIZE,
												tag.hasAttribute("fileAlignment") ? Integer.parseInt(tag.getAttribute("fileAlignment")) : Segment.DEFAULT_FILE_ALIGNMENT,
												tag.hasAttribute("fileAddress") ? Long.parseLong(tag.getAttribute("fileAddress"),16) : Segment.ANY_ADDRESS));
					}
				}
			}
		}
		return directory;
	}

	public void save() throws IOException {
		TreeMap<String,CodeFile> codeFiles = projectDirectory.getCodeFiles();
		TreeMap<String,Document> documents = projectDirectory.getDocuments();
		TreeMap<String,Directory> directories = projectDirectory.getDirectories();
		saveDirectory(projectFile.getParent(),codeFiles.values(),documents.values(),directories.values());
		saveProjectFile();
	}
	private void saveDirectory(String directory, Collection<CodeFile> codeFiles,Collection<Document> documents,Collection<Directory> directories) throws IOException {
		for (CodeFile codeFile : codeFiles) {
			for (int attempt=0;attempt<3;++attempt) {
				try {
					codeFile.getLanguage().getSaver().saveFile(codeFile);
					break;
				}
				catch (Throwable t) {
					// Saving didn't work, so try again
					t.printStackTrace();
					System.err.println("Saving "+codeFile.getName()+"failed.  "+((attempt<2)?"Trying again.":"Giving up after 3rd try."));
				}
			}
		}
		// TODO: Support saving documents
		for (Directory directory2 : directories) {
			saveDirectory(directory+File.separator+directory2.getName(),directory2.getCodeFiles().values(),directory2.getDocuments().values(),directory2.getDirectories().values());
		}
	}
	public void saveProjectFile() throws IOException {
		Tag root = new Tag("project");
		root.addAttribute("name",projectDirectory.getName().toString());
		root.addAttribute("outputType",outputType.toString());
		root.addAttribute("version",PwnIDE.VERSION_STRING);
		// TODO: Do some sort of actual version compatibility check here.
		root.addAttribute("min_version",PwnIDE.VERSION_STRING);
		for (Segment segment : segments) {
			Tag tag = new Tag("segment");
			tag.addAttribute("name",segment.getName().toString());
			tag.addAttribute("nBits",segment.getnBits().toString());
			tag.addAttribute("type",""+segment.getType());
			if (segment.getAlignment().get()!=Segment.DEFAULT_ALIGNMENT) {
				tag.addAttribute("alignment",segment.getAlignment().toString());
			}
			if (segment.getAddress().get()!=Segment.ANY_ADDRESS) {
				tag.addAttribute("address",Long.toString(segment.getAddress().get(),16));
			}
			if (segment.getMaxSize().get()!=Segment.NO_MAX_SIZE) {
				tag.addAttribute("maxSize",Long.toString(segment.getMaxSize().get(),16));
			}
			if (segment.getFileAlignment().get()!=Segment.DEFAULT_FILE_ALIGNMENT) {
				tag.addAttribute("fileAlignment",segment.getFileAlignment().toString());
			}
			if (segment.getFileAddress().get()!=Segment.ANY_ADDRESS) {
				tag.addAttribute("fileAddress",Long.toString(segment.getFileAddress().get(),16));
			}
			root.addChild(tag);
		}
		fillDirectoryTag(projectDirectory,root);
		XML xml = new XML(root);
		for (int attempt=0;attempt<3;++attempt) {
			try {
				xml.save(projectFile);
				break;
			}
			catch (Throwable t) {
				// Saving didn't work, so try again
				t.printStackTrace();
				System.err.println("Saving project file failed.  "+((attempt<2)?"Trying again.":"Giving up after 3rd try."));
			}
		}
	}
	private void fillDirectoryTag(Directory directory,Tag directoryTag) {
		TreeMap<String,CodeFile> codeFiles = directory.getCodeFiles();
		TreeMap<String,Document> documents = directory.getDocuments();
		TreeMap<String,Directory> directories = directory.getDirectories();
		for (CodeFile codeFile : codeFiles.values()) {
			Tag tag = new Tag("file");
			tag.addAttribute("name",codeFile.getName().toString());
			if (codeFile.getLanguage()==ASM.language) {
				tag.addAttribute("type","assembly");
			}
			if (openFiles.contains(codeFile)) {
				tag.addAttribute("open",openFiles.indexOf(codeFile)+"");
				if (codeFile==activeFile) {
					tag.addAttribute("active","true");
				}
			}
			CaretRef caretRef = codeFile.getCaretRef();
			if (caretRef.isTextCaret()) {
				TextCaret caret = (TextCaret)caretRef.get();
				Tag caretTag = new Tag("caret");
				caretTag.addAttribute("type","text");
				caretTag.addAttribute("function",caret.getLineList().getParent().getName().toString());
				caretTag.addAttribute("locType",""+caret.getLineList().getLocationType());
				caretTag.addAttribute("locIndex",""+caret.getLineList().getLocationIndex());
				caretTag.addAttribute("head_line",caret.getHeadLine()+"");
				caretTag.addAttribute("head_col",caret.getHeadCol()+"");
				caretTag.addAttribute("tail_line",caret.getTailLine()+"");
				caretTag.addAttribute("tail_col",caret.getTailCol()+"");
				caretTag.addAttribute("saved_x",caret.getSavedX()+"");
				tag.addChild(caretTag);
			}
			else if (caretRef.isHeaderCaret()) {
				HeaderCaret caret = (HeaderCaret)caretRef.get();
				Tag caretTag = new Tag("caret");
				caretTag.addAttribute("type","header");
				// FIXME: Figure out a good way to indicate the header caret in text.
			}
			directoryTag.addChild(tag);
		}
		for (Document document : documents.values()) {
			Tag tag = new Tag("file");
			tag.addAttribute("name",document.getName().toString());
			tag.addAttribute("type","document");
			if (openFiles.contains(document)) {
				tag.addAttribute("open",openFiles.indexOf(document)+"");
				if (document==activeFile) {
					tag.addAttribute("active","true");
				}
			}
			directoryTag.addChild(tag);
		}
		for (Directory directory2 : directories.values()) {
			Tag tag = new Tag("directory");
			tag.addAttribute("name",directory2.getName().toString());
			tag.addAttribute("expanded",directory2.isExpanded()+"");
			fillDirectoryTag(directory2,tag);
			directoryTag.addChild(tag);
		}
	}

	public Directory getDirectory() {
		return projectDirectory;
	}
	public File getFile() {
		return projectFile;
	}
	public String getPath() {
		return projectFile.getParent();
	}

	public Scope getScope() {
		return globalScope;
	}

	public HashSet<SyntaxError> getErrors() {
		return errors;
	}

	public HashSet<Caret> getUndefined() {
		return undefined;
	}

	public ObservedList<Segment> getSegments() {
		return segments;
	}
	public String[] getSegmentNamesArray() {
		String[] segmentNames = new String[segments.size()+1];
		segmentNames[0] = "";
		for (int i=0;i<segments.size();++i) {
			segmentNames[i+1] = segments.get(i).getName().toString();
		}
		return segmentNames;
	}

	public void updateNewCodeFile(CodeFile file) {
		file.setProject(this);	// This should be before adding global symbols otherwise, for instance, Structures won't be able to figure out their size
		ArrayList<Symbol> list = file.getAllGlobalSymbols();
		for (Symbol symbol : list) {
			if (!globalScope.contains(symbol)) {
				globalScope.add(symbol);
			}
		}
		// Update Structures and Constants first
		// NOTE: This is because they haven't been reparsed since getting access to language scope, so may cause errors in Functions or GlobalVariables
		for (Symbol symbol : list) {
			if (symbol instanceof Structure) {
				((Structure)symbol).nowHaveGlobalScope();
			}
			else if (symbol instanceof Constant) {
				((Constant)symbol).nowHaveGlobalScope();
			}
		}
		// Then update Functions and GlobalVariables
		for (Symbol symbol : list) {
			if (symbol instanceof Function) {
				((Function)symbol).nowHaveGlobalScope();
			}
			else if (symbol instanceof GlobalVariable) {
				((GlobalVariable)symbol).nowHaveGlobalScope();
			}
		}
	}

	public ArrayList<ProjectFile> getOpenFiles() {
		return openFiles;
	}

	public void setActiveFile(ProjectFile activeFile) {
		this.activeFile = activeFile;
	}

	public ProjectFile getActiveFile() {
		return activeFile;
	}

	public static String getNewProjectText(String name) {
		return "<?xml version=\"1.0\" encoding=\"UTF-8\"?><project min_version=\""+PwnIDE.VERSION_STRING+"\" name=\""+name+"\" version=\""+PwnIDE.VERSION_STRING+"\"></project>";
	}

	/**
	 * Selects the specified default segment based on the type.
	 * @param segmentType one of {@link Segment#CODE}, {@link Segment#INIT_DATA}, {@link Segment#UNINIT_DATA}, or {@link Segment#READ_ONLY_DATA}
	 * @return the default {@link Segment} of the specified type, or null if the type is invalid.
	 */
	public Segment getDefaultSegment(int segmentType) {
		if (segmentType==Segment.CODE) {
			return defaultCodeSegment;
		}
		if (segmentType==Segment.INIT_DATA) {
			return defaultInitDataSegment;
		}
		if (segmentType==Segment.UNINIT_DATA) {
			return defaultUninitDataSegment;
		}
		if (segmentType==Segment.READ_ONLY_DATA) {
			return defaultReadOnlyDataSegment;
		}
		return null;
	}

	/**
	 * Selects the segment with the specified name.
	 * @param name the segment name
	 * @return the {@link Segment} with the specified name, or null if none
	 */
	public Segment getSegment(String name) {
		for (Segment segment : segments) {
			if (segment.getName().toString().equals(name)) {
				return segment;
			}
		}
		return null;
	}

	/**
	 * Adds the specified segment, or if one of the same name already exists, merges the segment details.
	 * @param segment the segment to be added
	 * @return the segment kept if merging occurred, or the given segment if it was added directly
	 */
	public Segment addSegment(Segment segment) {
		Segment originalSegment = null;
		String name = segment.getName().toString();
		for (Segment seg : segments) {
			if (seg.getName().toString().equals(name)) {
				originalSegment = seg;
				break;
			}
		}
		// If no segment with same name, no merging needed.
		if (originalSegment==null) {
			segments.add(segment);
			// Look for default segment names.
			if (defaultCodeSegment==null && name.equals(Segment.DEFAULT_CODE_SEG_NAME)) {
				defaultCodeSegment = segment;
			}
			else if (defaultReadOnlyDataSegment==null && name.equals(Segment.DEFAULT_READ_ONLY_DATA_SEG_NAME)) {
				defaultReadOnlyDataSegment = segment;
			}
			else if (defaultInitDataSegment==null && name.equals(Segment.DEFAULT_INIT_DATA_SEG_NAME)) {
				defaultInitDataSegment = segment;
			}
			else if (defaultUninitDataSegment==null && name.equals(Segment.DEFAULT_UNINIT_DATA_SEG_NAME)) {
				defaultUninitDataSegment = segment;
			}
			return segment;
		}
		// If segment with same name, copy any non-default values.
		// NOTE: The segment type can't be changed at the moment; it wouldn't forseeably change here, but if it needs to, uncomment the following lines.
//		if (segment.getType()!=Segment.ANY_TYPE) {
//			originalSegment.setType(segment.getType());
//		}
		if (segment.getnBits().get()!=Segment.DEFAULT_N_BITS) {
			originalSegment.getnBits().set(segment.getnBits().get());
		}
		if (segment.getAlignment().get()!=Segment.DEFAULT_ALIGNMENT) {
			originalSegment.getAlignment().set(segment.getAlignment().get());
		}
		if (segment.getAddress().get()!=Segment.ANY_ADDRESS) {
			originalSegment.getAddress().set(segment.getAddress().get());
		}
		if (segment.getMaxSize().get()!=Segment.NO_MAX_SIZE) {
			originalSegment.getMaxSize().set(segment.getMaxSize().get());
		}
		// FIXME: Handle readable/writable/executable/shared/no-cache/no-page/discardable Segment flags.
		return originalSegment;
	}
	public Function getEntryPoint() {
		if (entryPoint.getReferences()[0] instanceof Function) {
			return (Function)entryPoint.getReferences()[0];
		}
		return null;
	}
	public SingleLine getEntryPointLine() {
		return entryPoint;
	}

	public void symbolAdded(Scope scope, Symbol symbol) {
		if (entryPoint.getReferences()[0]==null && symbol instanceof Function && symbol.getName().length()>0 && symbol.getName().equals(entryPoint)) {
			entryPoint.getReferences()[0] = symbol;
		}
	}

	public void symbolRemoved(Scope scope, Symbol symbol) {
		if (symbol== entryPoint.getReferences()[0]) {
			String text = entryPoint.toString();
			Function newReference = null;
			ArrayList<Symbol> symbols = globalScope.get(text);
			if (symbols!=null) {
				for (Symbol s : symbols) {
					if (s instanceof Function) {
						newReference = (Function)s;
						break;
					}
				}
			}
			entryPoint.getReferences()[0] = newReference;
		}
	}

	public void symbolChanged(Scope scope, SymbolChangeEvent e) {
		Symbol symbol = e.getSymbol();
		if (e instanceof SymbolRenameEvent && symbol instanceof Function) {
			if (symbol== entryPoint.getReferences()[0]) {
				entryPoint.replaceAll(symbol.getName().toString());
			}
			else if (entryPoint.getReferences()[0]==null && symbol.getName().length()>0 && symbol.getName().equals(entryPoint)) {
				entryPoint.getReferences()[0] = symbol;
			}
		}
	}

	public void valueChanged(SingleLine line, String oldValue) {
		String text = entryPoint.toString();
		Function newReference = null;
		ArrayList<Symbol> symbols = globalScope.get(text);
		if (symbols!=null) {
			for (Symbol s : symbols) {
				if (s instanceof Function) {
					newReference = (Function)s;
					break;
				}
			}
		}
		entryPoint.getReferences()[0] = newReference;
	}

	public ObservedList<ExternalInclude> getLibraryIncludes() {
		return libraryIncludes;
	}

	public ObservedList<ExternalInclude> getImportedIncludes() {
		return importedIncludes;
	}

	public ObservedInt getOutputType() {
		return outputType;
	}

	public static class ExternalInclude extends Rowable {
		/**
		 * This is because Java can't do virtual static functions
		 */
		public static final ExternalInclude	DUMMY = new ExternalInclude("","",null,0);

		public static final int	NAME_INDEX			= 0;
		public static final int	DESCRIPTION_INDEX	= 1;
		public static final int	NUM_STRINGS			= 2;

		public ExternalInclude(String name,String description,CodeFileChunk parent,int row) {
			super(NUM_STRINGS,parent,CodeFileChunk.LOCATION_NOWHERE,row);
			strings[NAME_INDEX] = new SingleLine(name.toCharArray(),parent,CodeFileChunk.LOCATION_NOWHERE,NAME_INDEX+(row<<16));
			strings[DESCRIPTION_INDEX] = new SingleLine(description.toCharArray(),parent,CodeFileChunk.LOCATION_NOWHERE,DESCRIPTION_INDEX+(row<<16));
		}
		public SingleLine getName() {
			return strings[NAME_INDEX];
		}
		public SingleLine getDescription() {
			return strings[DESCRIPTION_INDEX];
		}
		public ExternalInclude copy(CodeFileChunk parent,int row) {
			return new ExternalInclude(getName().toString(),getDescription().toString(),parent,row);
		}

		public boolean equals(Object o) {
			if (!(o instanceof ExternalInclude)) {
				return false;
			}
			ExternalInclude that = (ExternalInclude)o;
			return strings[NAME_INDEX].equals(that.strings[NAME_INDEX]) && strings[DESCRIPTION_INDEX].equals(that.strings[DESCRIPTION_INDEX]);
		}
	}
}
